/*******************************************************************************
 * Copyright (c) 2008, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ceylon.ide.eclipse.core.debug.hover;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.debug.internal.ui.SWTFactory;
import org.eclipse.debug.internal.ui.model.elements.ElementContentProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer;
import org.eclipse.debug.internal.ui.views.variables.details.DefaultDetailPane;
import org.eclipse.debug.internal.ui.views.variables.details.DetailPaneProxy;
import org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer;
import org.eclipse.debug.ui.AbstractDebugView;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.text.AbstractInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IInformationControlExtension2;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.LocationListener;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPartSite;

import org.eclipse.ceylon.ide.eclipse.ui.CeylonPlugin;
import org.eclipse.ceylon.ide.eclipse.util.DocBrowser;
import org.eclipse.ceylon.ide.eclipse.util.EditorUtil;

/**
 * Creates an information control to display an expression in a hover control.
 * 
 * @noextend This class is not intended to be subclassed by clients.
 * 
 * @since 3.3
 */
public class ExpressionInformationControlCreator implements IInformationControlCreator {

    private static final String SETTINGS_ID = 
            CeylonPlugin.PLUGIN_ID + ".expressionHover";
    
    /**
     * Returns the dialog settings for this hover or <code>null</code> if none
     * 
     * @param create whether to create the settings
     */
    private static IDialogSettings getDialogSettings(boolean create) {
        IDialogSettings settings = CeylonPlugin.getInstance().getDialogSettings();
        IDialogSettings section = settings.getSection(SETTINGS_ID);
        if (section == null & create) {
            section = settings.addNewSection(SETTINGS_ID);
        }
        return section;
    }
    
    class ExpressionInformationControl extends AbstractInformationControl implements IInformationControlExtension2 {

        /**
         * Dialog setting key for height
         */
        private static final String HEIGHT = "HEIGHT";

        /**
         * Dialog setting key for width. 
         */
        private static final String WIDTH = "WIDTH";

        /**
         * Dialog setting key for tree sash weight
         */
        private static final String SASH_WEIGHT_TREE = "SashWeightTree";
        
        /**
         * Dialog setting key for details sash weight
         */
        private static final String SASH_WEIGHT_DETAILS = "SashWeightDetails";        
        
        /**
         * Dialog setting key for tree sash weight
         */
        private static final String SASH_WEIGHT_DOC = "SashWeightDoc";
        
        /**
         * Dialog setting key for details sash weight
         */
        private static final String SASH_WEIGHT_DEBUG = "SashWeightDebug";        
        
        /**
         * Variable to display.
         */
        private IVariable fVariable;
        
        public IVariable getVariable() {
            return fVariable;
        }
        
        private IPresentationContext fContext;
        private TreeModelViewer fViewer;
        private SashForm fSashForm;
        private Composite fDetailPaneComposite;
        private DetailPaneProxy fDetailPane;
        private Tree fTree;

        private SashForm fSashForm2;

        private DocBrowser fBrowser;
            
        /**
         * Creates the content for the root element of the tree viewer in the hover
         */
        private class TreeRoot extends ElementContentProvider {
            /* (non-Javadoc)
             * @see org.eclipse.debug.internal.ui.viewers.model.provisional.elements.ElementContentProvider#getChildCount(java.lang.Object, org.eclipse.debug.internal.ui.viewers.provisional.IPresentationContext)
             */
            @Override
            protected int getChildCount(Object element, IPresentationContext context, IViewerUpdate monitor) throws CoreException {
                return 1;
            }
            /* (non-Javadoc)
             * @see org.eclipse.debug.internal.ui.viewers.model.provisional.elements.ElementContentProvider#getChildren(java.lang.Object, int, int, org.eclipse.debug.internal.ui.viewers.provisional.IPresentationContext)
             */
            @Override
            protected Object[] getChildren(Object parent, int index, int length, IPresentationContext context, IViewerUpdate monitor) throws CoreException {
                return new Object[] { fVariable };
            }
            
            /* (non-Javadoc)
             * @see org.eclipse.debug.internal.ui.viewers.model.provisional.elements.ElementContentProvider#supportsContextId(java.lang.String)
             */
            @Override
            protected boolean supportsContextId(String id) {
                return true;
            }
        }
        
        /**
         * Inner class implementing IDetailPaneContainer methods.  Handles changes to detail
         * pane and provides limited access to the detail pane proxy.
         */
        private class DetailPaneContainer implements IDetailPaneContainer{
        
            /* (non-Javadoc)
             * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getCurrentPaneID()
             */
            public String getCurrentPaneID() {
                return fDetailPane.getCurrentPaneID();
            }
        
            /* (non-Javadoc)
             * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getCurrentSelection()
             */
            public IStructuredSelection getCurrentSelection() {
                return (IStructuredSelection)fViewer.getSelection();
            }
        
            /* (non-Javadoc)
             * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#refreshDetailPaneContents()
             */
            public void refreshDetailPaneContents() {       
                fDetailPane.display(getCurrentSelection());
            }
        
            /* (non-Javadoc)
             * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getParentComposite()
             */
            public Composite getParentComposite() {
                return fDetailPaneComposite;
            }
        
            /* (non-Javadoc)
             * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getWorkbenchPartSite()
             */
            public IWorkbenchPartSite getWorkbenchPartSite() {
                return null;
            }
        
            /* (non-Javadoc)
             * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#paneChanged(java.lang.String)
             */
            public void paneChanged(String newPaneID) {
                if (newPaneID.equals(DefaultDetailPane.ID)){
                    fDetailPane.getCurrentControl().setForeground(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
                    fDetailPane.getCurrentControl().setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
                }
            }
        
        }       

        /**
         * Constructs a new control in the given shell.
         * 
         * @param parentShell shell
         * @param resize whether resize is supported
         */
        ExpressionInformationControl(Shell parentShell, boolean resize) {
            super(parentShell, resize);
            create();
        }
        
        public void addLocationListener(LocationListener listener) {
            fBrowser.addLocationListener(listener);
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.text.AbstractInformationControl#computeSizeHint()
         */
        @Override
        public Point computeSizeHint() {
            IDialogSettings settings = getDialogSettings(false);
            if (settings != null) {
                int x = getIntSetting(settings, WIDTH);
                if (x > 0) {
                    int y = getIntSetting(settings, HEIGHT);
                    if (y > 0) {
                        return new Point(x,y);
                    }
                }
            }
            return super.computeSizeHint();
        }

        /**
         * Returns an integer value in the given dialog settings or -1 if none.
         * 
         * @param settings dialog settings
         * @param key key
         * @return value or -1 if not present
         */
        private int getIntSetting(IDialogSettings settings, String key) {
            try {
                return settings.getInt(key);
            } catch (NumberFormatException e) {
                return -1;
            }
        }
        
        /* (non-Javadoc)
         * @see org.eclipse.jface.text.AbstractInformationControl#dispose()
         */
        @Override
        public void dispose() {
            persistSettings(getShell());
            fContext.dispose();
            super.dispose();
        }

        /**
         * Persists dialog settings.
         * 
         * @param shell
         */
        private void persistSettings(Shell shell) {
            if (shell != null && !shell.isDisposed()) {
                if (isResizable()) {
                    IDialogSettings settings = getDialogSettings(true);
                    Point size = shell.getSize();
                    settings.put(WIDTH, size.x);
                    settings.put(HEIGHT, size.y);
                    int[] weights = fSashForm.getWeights();
                    settings.put(SASH_WEIGHT_TREE, weights[0]);
                    settings.put(SASH_WEIGHT_DETAILS, weights[1]);
                    int[] weights2 = fSashForm2.getWeights();
                    settings.put(SASH_WEIGHT_DOC, weights2[0]);
                    settings.put(SASH_WEIGHT_DEBUG, weights2[1]);
                }
            }
        }
        
        /* (non-Javadoc)
         * @see org.eclipse.jface.text.AbstractInformationControl#setVisible(boolean)
         */
        @Override
        public void setVisible(boolean visible) {
            if (!visible) {     
                persistSettings(getShell());
            }
            super.setVisible(visible);
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.text.AbstractInformationControl#createContent(org.eclipse.swt.widgets.Composite)
         */
        @Override
        protected void createContent(Composite parent) {
            
            fSashForm2 = new SashForm(parent, parent.getStyle());
            fSashForm2.setOrientation(SWT.HORIZONTAL);
            
            fBrowser = new DocBrowser(fSashForm2, SWT.NONE);
            
            fSashForm = new SashForm(fSashForm2, parent.getStyle());
            fSashForm.setOrientation(SWT.VERTICAL);

            // update presentation context
            AbstractDebugView view = getViewToEmulate();
            fContext = new PresentationContext(IDebugUIConstants.ID_VARIABLE_VIEW);
            if (view != null) {
                // copy over properties
                IPresentationContext copy = ((TreeModelViewer)view.getViewer()).getPresentationContext();
                String[] properties = copy.getProperties();
                for (int i = 0; i < properties.length; i++) {
                    String key = properties[i];
                    fContext.setProperty(key, copy.getProperty(key));
                }
            }
           
            fViewer = new TreeModelViewer(fSashForm, SWT.NO_TRIM | SWT.MULTI | SWT.VIRTUAL, fContext);
            fViewer.setAutoExpandLevel(1);
            
            if (view != null) {
                // copy over filters
                StructuredViewer structuredViewer = (StructuredViewer) view.getViewer();
                if (structuredViewer != null) {
                    ViewerFilter[] filters = structuredViewer.getFilters();
                    for (int i = 0; i < filters.length; i++) {
                        fViewer.addFilter(filters[i]);
                    }
                }
            }

            fDetailPaneComposite = SWTFactory.createComposite(fSashForm, 1, 1, GridData.FILL_BOTH);
            Layout layout = fDetailPaneComposite.getLayout();
            if (layout instanceof GridLayout) {
                GridLayout gl = (GridLayout) layout;
                gl.marginHeight = 0;
                gl.marginWidth = 0;
            }
            
            fDetailPane = new DetailPaneProxy(new DetailPaneContainer());
            fDetailPane.display(null); // Bring up the default pane so the user doesn't see an empty composite
          
            fTree = fViewer.getTree();
            fTree.addSelectionListener(new SelectionListener() {
                public void widgetSelected(SelectionEvent e) {
                    fDetailPane.display((IStructuredSelection)fViewer.getSelection());
                }
                public void widgetDefaultSelected(SelectionEvent e) {}
            });

            initSashWeights();
                  
            // add update listener to auto-select and display details of root expression
            fViewer.addViewerUpdateListener(new IViewerUpdateListener() {
                public void viewerUpdatesComplete() {
                }       
                public void viewerUpdatesBegin() {
                }
                public void updateStarted(IViewerUpdate update) {
                }
                public void updateComplete(IViewerUpdate update) {
                    if (update instanceof IChildrenUpdate) {
                        TreeSelection selection = new TreeSelection(new TreePath(new Object[]{fVariable}));
                        fViewer.setSelection(selection);
                        fDetailPane.display(selection);
                        fViewer.removeViewerUpdateListener(this);
                    }
                }
            });        
            
            setForegroundColor(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
            setBackgroundColor(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
        }
        

        /**
         * Attempts to find an appropriate view to emulate, this will either be the
         * variables view or the expressions view.
         * @return a view to emulate or <code>null</code>
         */
        private AbstractDebugView getViewToEmulate() {
            IWorkbenchPage page = EditorUtil.getActivePage();
            AbstractDebugView expressionsView = (AbstractDebugView) page.findView(IDebugUIConstants.ID_EXPRESSION_VIEW);
            if (expressionsView != null && expressionsView.isVisible()) {
                return expressionsView;
            }
            AbstractDebugView variablesView = (AbstractDebugView) page.findView(IDebugUIConstants.ID_VARIABLE_VIEW);
            if (variablesView != null && variablesView.isVisible()) {
                return variablesView;
            }
            if (expressionsView != null) {
                return expressionsView;
            }
            return variablesView;
        }   

        /**
         * Initializes the sash form weights from the preference store (using default values if 
         * no sash weights were stored previously).
         */
        protected void initSashWeights(){
            IDialogSettings settings = getDialogSettings(false);
            if (settings != null) {
                int doc = getIntSetting(settings, SASH_WEIGHT_DOC);
                if (doc > 0) {
                    int debug = getIntSetting(settings, SASH_WEIGHT_DEBUG);
                    if (debug > 0) {
                        fSashForm2.setWeights(new int[]{doc, debug});
                    }
                }
                int tree = getIntSetting(settings, SASH_WEIGHT_TREE);
                if (tree > 0) {
                    int details = getIntSetting(settings, SASH_WEIGHT_DETAILS);
                    if (details > 0) {
                        fSashForm.setWeights(new int[]{tree, details});
                    }
                }
            }
        }
                
        /* (non-Javadoc)
         * @see org.eclipse.jface.text.AbstractInformationControl#setForegroundColor(org.eclipse.swt.graphics.Color)
         */
        @Override
        public void setForegroundColor(Color foreground) {
            super.setForegroundColor(foreground);
            fDetailPaneComposite.setForeground(foreground);
            fTree.setForeground(foreground);
        }
        
        /* (non-Javadoc)
         * @see org.eclipse.jface.text.AbstractInformationControl#setBackgroundColor(org.eclipse.swt.graphics.Color)
         */
        @Override
        public void setBackgroundColor(Color background) {
            super.setBackgroundColor(background);
            fDetailPaneComposite.setBackground(background);
            fTree.setBackground(background);
        }
    
        /* (non-Javadoc)
         * @see org.eclipse.jface.text.AbstractInformationControl#setFocus()
         */
        @Override
        public void setFocus() {
            super.setFocus();
            fTree.setFocus();
        }
        
        /* (non-Javadoc)
         * @see org.eclipse.jface.text.IInformationControlExtension#hasContents()
         */
        public boolean hasContents() {
            return fVariable != null;
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.text.IInformationControlExtension2#setInput(java.lang.Object)
         */
        public void setInput(Object input) {
            if (input instanceof DebugHoverInput) {
                DebugHoverInput inputs = (DebugHoverInput) input;
                fVariable = inputs.getVariable();
                fViewer.setInput(new TreeRoot());
                fBrowser.setText(inputs.getText());
            }
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.text.AbstractInformationControl#getInformationPresenterControlCreator()
         */
        @Override
        public ExpressionInformationControlCreator getInformationPresenterControlCreator() {
            return new ExpressionInformationControlCreator() {
                /* (non-Javadoc)
                 * @see org.eclipse.jdt.internal.debug.ui.ExpressionInformationControlCreator#createInformationControl(org.eclipse.swt.widgets.Shell)
                 */
                @Override
                public ExpressionInformationControl createInformationControl(Shell shell) {
                    return new ExpressionInformationControl(shell, true);
                }
            };
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.text.IInformationControlCreator#createInformationControl(org.eclipse.swt.widgets.Shell)
     */
    public ExpressionInformationControl createInformationControl(Shell parent) {
        return new ExpressionInformationControl(parent, false);
    }


}
